package com.magenta.maxoptra.gis.commons;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;

import java.io.Serializable;
import java.util.regex.Pattern;

public class Logger implements Serializable {
    private String className;

    private transient org.apache.logging.log4j.Logger logger;
    private static final String FQCN = Logger.class.getName();
    private boolean showClassReference = true;

    public Logger(Class clazz) {
        this(clazz.getName());
    }

    public Logger(String className) {
        this.className = className;
    }

    public Logger(Class clazz, boolean showClassReference) {
        this(clazz);
        this.showClassReference = showClassReference;
    }

    public void error(String msg) {
        msg = (msg == null ? "" : msg);
        getLogger().error(getClassName(), Level.ERROR, msg, null);
    }

    public void error(String msg, Throwable cause) {
        getLogger().error(msg, cause);
    }

    public void error(String msg, Throwable cause, Object... objs) {
        if (getLogger().isErrorEnabled()) {
            getLogger().error(formatMsg(msg, objs), cause);
        }
    }

    public void error(String msg, Object... objs) {
        if (getLogger().isErrorEnabled()) {
            getLogger().error(formatMsg(msg, objs));
        }
    }

    public void error(Throwable cause) {
        getLogger().error(cause);
    }

    public void errorWithTrace(Throwable cause) {
        getLogger().error(StringUtils.convertStackTraceToString(cause));
    }

    public void warn(String msg) {
        getLogger().warn(msg);
    }

    public void warnX(String msg, Throwable throwable, Object... objs) {
        if (getLogger().isWarnEnabled()) {
            String formatedMessage = formatMsg(msg, objs);
            warnX(formatedMessage, throwable);
        }
    }

    public void warnX(String msg, Throwable throwable) {
        final String finalMessage;
        String bugDescription = maxoptraBugDescription(throwable);
        if (bugDescription != null) {
            finalMessage = msg + bugDescription;
        } else {
            finalMessage = msg + throwable.getLocalizedMessage();
        }
        getLogger().warn(finalMessage);
    }

    private static final Pattern maxoptraClasses = Pattern.compile("^com.magenta.maxoptra.*");

    private String maxoptraBugDescription(Throwable throwable) {
        Throwable realCause = throwable;
        //FIND REAL CAUSE
        while (realCause.getCause() != null) {
            realCause = realCause.getCause();
        }
        StackTraceElement[] trace = realCause.getStackTrace();
        if (trace != null) {
            //FIND MAXOPTRA CODE POINT
            for (StackTraceElement traceElement : trace) {
                String className = traceElement.getClassName();
                if (maxoptraClasses.matcher(className).matches()) {
                    int lineNumber = traceElement.getLineNumber();
                    String methodName = traceElement.getMethodName();
                    String fileName = traceElement.getFileName();
                    //BASIC MESSAGE
                    String throwMessage = realCause.getLocalizedMessage();
                    String throwInfo = abbrevia(realCause.getClass()) + (throwMessage != null ? '[' + throwMessage + ']' : "");
                    String basicInfo = throwInfo + ' ' + "at" + ' ' + className + '.' + methodName;
                    //CODE POINT INFO
                    if (fileName != null || lineNumber > 0) {
                        StringBuilder availableDebugInfo = new StringBuilder(basicInfo);
                        availableDebugInfo.append('(');
                        if (fileName != null) {
                            availableDebugInfo.append(fileName);
                        }
                        if (lineNumber > 0) {
                            availableDebugInfo.append(':').append(lineNumber);
                        }
                        availableDebugInfo.append(')');
                        return availableDebugInfo.toString();
                    }
                    return basicInfo;
                }
            }
        }
        return null;
    }

    private String abbrevia(Class<?> clazz) {
        StringBuilder aab = new StringBuilder(clazz.getPackage().getName());
        aab.append('.');
        String simpleName = clazz.getSimpleName();
        int nameLength = simpleName.length();
        char[] name = new char[nameLength];
        simpleName.getChars(0, nameLength, name, 0);
        for (char ch : name) {
            if (Character.isUpperCase(ch) || Character.isDigit(ch)) {
                aab.append(Character.valueOf(ch));
            }
        }
        return aab.toString();
    }

    public void warn(String msg, Object... args) {
        if (getLogger().isWarnEnabled()) {
            getLogger().warn(formatMsg(msg, args));
        }
    }

    public void info(String msg) {
        getLogger().info(msg);
    }

    public void info(String msg, Object... args) {
        if (getLogger().isInfoEnabled()) {
            getLogger().info(formatMsg(msg, args));
        }
    }

    public void fatal(String msg, Throwable cause) {
        getLogger().fatal(msg, cause);
    }

    public void fatal(String msg, Throwable cause, Object... args) {
        if (getLogger().isFatalEnabled()) {
            getLogger().fatal(formatMsg(msg, args), cause);
        }
    }

    private String getClassName() {
        return (className!=null ? className : FQCN);
    }

    public void debug(String msg) {
        getLogger().debug(msg);
    }

    public void debug(String msg, Object... args) {
        if (getLogger().isDebugEnabled()) {
            getLogger().debug(formatMsg(msg, args));
        }
    }

    public void logElapsedTime(String message, long startTime) {
        if (getLogger().isDebugEnabled()) {
            debug("[" + (System.currentTimeMillis() - startTime) + " ms] " + message);
        }
    }

    public void trace(String msg) {
        getLogger().trace(msg);
    }

    public void trace(String msg, Object... args) {
        if (getLogger().isTraceEnabled()) {
            getLogger().trace(formatMsg(msg, args));
        }
    }

    public org.apache.logging.log4j.Logger getLogger() {
        if (logger == null) {
            logger = LogManager.getLogger(className);
        }
        return logger;
    }

    protected String formatMsg(String msg) {
        return msg;
    }

    private String formatMsg(String msg, Object... objs) {
        String result = String.format(msg, objs);
        if (showClassReference) {
            result += " at " + Thread.currentThread().getStackTrace()[3].toString();
        }
        return result;
    }

}
